import torch
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

'''
在输出为一个标量的情况下，可以调用输入tensor的backward()方法，但是在数据是向量的时候，调用backward()还需要传入其他参数

tensor.data 获取tensor中的数据：
    required_grad=True 的时候，获取数据内容，不带grad等属性
    required_grad=False 的时候，tensor.data与tensor等价
tensor.numpy():
    required_grad=True 不能够直接转换，需要调用tensor.detach().numpy()
    tensor.detach().numpy() 能够实现对tensor中的数据的深拷贝（开辟新的内存进行存放），转化为ndarray类型
'''

'''
1. 准备数据
2. 计算预测值
3. 计算损失，把参数的梯度置为0，进行反向传播
4. 更新参数
'''

lr = 0.01

# 1. 准备数据 y = 3x + 0.8 和参数
x = torch.rand([500, 1])  # rand -> 初始化0到1的数据；500行1列
y_true = 3 * x + 0.8

# 2. 通过模型计算y_predict
w = torch.rand([1, 1], requires_grad=True)  # x * w -> 500 * 1
b = torch.tensor(0, requires_grad=True, dtype=torch.float32)

# 4. 通过循环，反向传播，更新参数
for i in range(2000):
    # 每次更新参数后，重新计算loss
    # 3. 计算loss
    y_predict = torch.matmul(x, w) + b
    loss = (y_true - y_predict).pow(2).mean()

    # 判断当前x和y的gradient是否是个数字 => 需要把结果置为0
    # BP中无需梯度累加
    if w.grad is not None:
        w.grad.data.zero_()  # _ 表示原地修改
    if b.grad is not None:
        b.grad.data.zero_()

    loss.backward()  # 反向传播
    w.data = w.data - w.grad * lr
    b.data = b.data - b.grad * lr
    if i % 100 == 0:
        print('w=%s, b=%s, loss=%s' % (w.item(), b.item(), loss.item()))

# 5. 画图
plt.figure(figsize=(20, 8))
plt.scatter(x.numpy().reshape(-1), y_true.numpy().reshape(-1))  # 将x和y转化为1阶便于画图
y_predict = torch.matmul(x, w) + b
plt.plot(x.numpy().reshape(-1), y_predict.detach().numpy().reshape(-1), c='r')
plt.show()

'''
# 1. 准备数据 y = 3x + 0.8 和参数
x = torch.rand([500, 1])
y = 3 * x + 0.8

# 2. 通过模型计算y_predict
w = torch.rand(1, requires_grad=True)
b = torch.tensor(1, requires_grad=True)

def loss_fn(y, y_predict):
    loss = (y - y_predict).pow(2).mean()
    for i in [w, b]:
        # 每次反向传播前，把梯度置为0
        if i.grad is not None:
            # 如果不是None，表明之前加过梯度，需要重置为0才能进行反向传播：因为x.grad是累加梯度直到输入层，而BP反向传播是一层重新计算一次梯度
            i.grad.data.zero_()
    # [i.grad.data.zero_() for i in [w, b] if i.grad is not None]
    loss.backward()
    return loss

def optimize(learning_rate):
    w.data -= w.grad * learning_rate
    b.data -= b.grad * learning_rate


for i in range(2000):
    # 2. 计算预测值
    y_predict = w * x + b
    # 3. 计算loss，重置梯度，BP
    loss = loss_fn(y, y_predict)

    if i % 500 == 0:
        print(i, loss)
    # 4. 更新参数w, b
    optimize(0.01)

# 绘图    
predict = x * w + b
plt.scatter(x.numpy(), y_true.numpy())
y_predict = torch.matmul(x, w) + b
plt.plot(x.numpy(), predict.numpy(), c='r')
plt.show()

print(w, b)
'''
